{
 "cells": [
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "---\n",
    "hide_table_of_contents: true\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Custom chat history\n",
    "\n",
    "To create your own custom chat history class for a backing store, you can extend the [`BaseListChatMessageHistory`](https://api.js.langchain.com/classes/langchain_core_chat_history.BaseListChatMessageHistory.html) class. This requires you to implement the following methods:\n",
    "\n",
    "- `addMessage`, which adds a `BaseMessage` to the store for the current session. This usually involves serializing them into a simple object representation (defined as `StoredMessage` below) that the backing store can handle.\n",
    "- `getMessages`, which loads messages for a session and returns them as an array of `BaseMessage`s. For most databases, this involves deserializing stored messages into `BaseMessage`s.\n",
    "\n",
    "In addition, there are some optional methods that are nice to override:\n",
    "\n",
    "- `clear`, which removes all messages from the store.\n",
    "- `addMessages`, which will add multiple messages at a time to the current session. This can save round-trips to and from the backing store if many messages are being saved at once. The default implementation will call `addMessage` once per input message.\n",
    "\n",
    "Here's an example that stores messages in-memory. For long-term persistence, you should use a real database. You'll notice we use the `mapChatMessagesToStoredMessages` and `mapStoredMessagesToChatMessages` helper methods for consistent serialization and deserialization:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import { BaseListChatMessageHistory } from \"@langchain/core/chat_history\";\n",
    "import {\n",
    "  BaseMessage,\n",
    "  StoredMessage,\n",
    "  mapChatMessagesToStoredMessages,\n",
    "  mapStoredMessagesToChatMessages,\n",
    "} from \"@langchain/core/messages\";\n",
    "\n",
    "// Not required, but usually chat message histories will handle multiple sessions\n",
    "// for different users, and should take some kind of sessionId as input.\n",
    "export interface CustomChatMessageHistoryInput {\n",
    "  sessionId: string;\n",
    "};\n",
    "\n",
    "export class CustomChatMessageHistory extends BaseListChatMessageHistory {\n",
    "  lc_namespace = [\"langchain\", \"stores\", \"message\"];\n",
    "  \n",
    "  sessionId: string;\n",
    "\n",
    "  // Simulate a real database layer. Stores serialized objects.\n",
    "  fakeDatabase: Record<string, StoredMessage[]> = {};\n",
    "\n",
    "  constructor(fields: CustomChatMessageHistoryInput) {\n",
    "    super(fields);\n",
    "    this.sessionId = fields.sessionId;\n",
    "  }\n",
    "\n",
    "  async getMessages(): Promise<BaseMessage[]> {\n",
    "    const messages = this.fakeDatabase[this.sessionId] ?? [];\n",
    "    return mapStoredMessagesToChatMessages(messages);\n",
    "  }\n",
    "\n",
    "  async addMessage(message: BaseMessage): Promise<void> {\n",
    "    if (this.fakeDatabase[this.sessionId] === undefined) {\n",
    "      this.fakeDatabase[this.sessionId] = [];\n",
    "    }\n",
    "    const serializedMessages = mapChatMessagesToStoredMessages([message]);\n",
    "    this.fakeDatabase[this.sessionId].push(serializedMessages[0]);\n",
    "  }\n",
    "\n",
    "  async addMessages(messages: BaseMessage[]): Promise<void> {\n",
    "    if (this.fakeDatabase[this.sessionId] === undefined) {\n",
    "      this.fakeDatabase[this.sessionId] = [];\n",
    "    }\n",
    "    const existingMessages = this.fakeDatabase[this.sessionId];\n",
    "    const serializedMessages = mapChatMessagesToStoredMessages(messages);\n",
    "    this.fakeDatabase[this.sessionId] = existingMessages.concat(serializedMessages);\n",
    "  }\n",
    "\n",
    "  async clear(): Promise<void> {\n",
    "    delete this.fakeDatabase[this.sessionId];\n",
    "  }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can then use this chat history as usual:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[\n",
       "  HumanMessage {\n",
       "    lc_serializable: \u001b[33mtrue\u001b[39m,\n",
       "    lc_kwargs: { content: \u001b[32m\"Hello there!\"\u001b[39m, additional_kwargs: {} },\n",
       "    lc_namespace: [ \u001b[32m\"langchain_core\"\u001b[39m, \u001b[32m\"messages\"\u001b[39m ],\n",
       "    content: \u001b[32m\"Hello there!\"\u001b[39m,\n",
       "    name: \u001b[90mundefined\u001b[39m,\n",
       "    additional_kwargs: {}\n",
       "  },\n",
       "  AIMessage {\n",
       "    lc_serializable: \u001b[33mtrue\u001b[39m,\n",
       "    lc_kwargs: { content: \u001b[32m\"Hello to you too!\"\u001b[39m, additional_kwargs: {} },\n",
       "    lc_namespace: [ \u001b[32m\"langchain_core\"\u001b[39m, \u001b[32m\"messages\"\u001b[39m ],\n",
       "    content: \u001b[32m\"Hello to you too!\"\u001b[39m,\n",
       "    name: \u001b[90mundefined\u001b[39m,\n",
       "    additional_kwargs: {}\n",
       "  }\n",
       "]"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import { AIMessage, HumanMessage } from \"@langchain/core/messages\";\n",
    "\n",
    "const chatHistory = new CustomChatMessageHistory({ sessionId: \"test\" });\n",
    "\n",
    "await chatHistory.addMessages([\n",
    "  new HumanMessage(\"Hello there!\"),\n",
    "  new AIMessage(\"Hello to you too!\")\n",
    "]);\n",
    "\n",
    "await chatHistory.getMessages();"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Deno",
   "language": "typescript",
   "name": "deno"
  },
  "language_info": {
   "file_extension": ".ts",
   "mimetype": "text/x.typescript",
   "name": "typescript",
   "nb_converter": "script",
   "pygments_lexer": "typescript",
   "version": "5.3.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
